﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.Evaluator;
using SSharp.Core.DataTypes;
using System.Reflection;
using SSharp.Core.Util;

namespace SSharp.Core.Builtins.Macros {
	class New : Macro {
		private static Dictionary<Symbol, Type> typeCache = new Dictionary<Symbol,Type>();

		public object Expand(IList<object> args) {
			if (args.Count < 1) {
				throw new SyntaxError("new expects one or more arguments, given 0");
			}

			Symbol name = args[0] as Symbol;
			if (name == null) {
				throw new SyntaxError("new expects symbol as first argument; given " + args[0]);
			}

			Type type = FindType(name);

			// create the obj
			object obj = type.InvokeMember(".ctor",
				BindingFlags.CreateInstance,
				null,
				null,
				args.Skip(1).ToArray());

			// wrap with a DotNetObject macro
			return DotNetObject.Wrap(obj);
		}

		/// <summary>
		/// Returns the Type object given a type name.
		/// </summary>
		private static Type FindType(Symbol name) {
			Type result;
			if (typeCache.TryGetValue(name, out result)) {
				// we already have the type in the cache
				return result;
			}

			// we have to search all loaded assemblies for the given type name, and make sure there's only one such type name.
			result = AppDomain.CurrentDomain.GetAssemblies().SelectMany(assy => GetTypeFromAssembly(assy, name)).SingleOrDefault();
			if (result == null) {
				// we don't cache bad results; they may be changed by loading more assemblies
				throw new TypeLoadException("Type not found: " + name);
			}

			typeCache[name] = result;
			return result;
		}

		/// <summary>
		/// Returns assy.GetType(), wrappen in an IEnumerable returning 0 or 1 results, for SelectMany.
		/// </summary>
		private static IEnumerable<Type> GetTypeFromAssembly(Assembly assy, string name) {
			Type result = assy.GetType(name);
			if (result != null) {
				// if the type exists, returns it as one result. Otherwise, returns zero results.
				yield return result;
			}
		}
	}
}
